/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package iamutkarshtiwari.github.io.ananas.editimage.view;

import iamutkarshtiwari.github.io.ananas.editimage.utils.Matrix3;
import iamutkarshtiwari.github.io.ananas.editimage.utils.PxUtil;
import iamutkarshtiwari.github.io.ananas.editimage.utils.RectUtil;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.TouchEvent;

/**
 * 图片裁剪自定义view
 */
public class CropImageView extends Component implements Component.EstimateSizeListener, Component.DrawTask, Component.TouchEventListener {
    private static final String TAG = CropImageView.class.getSimpleName();

    private float last_x;
    private float last_y;
    private Paint mPaint;


    private Paint mPaintLine;

    private Paint mPaintBg;
    private RectFloat drawRect;
    private PixelMap mDrawBit;

    private final int insideRect = 0;
    private final int onRectXL = 1;
    private final int onRectXR = 2;
    private final int onRectYT = 3;
    private final int onRectYB = 4;
    private final int onRectConcerLt = 5;
    private final int onRectConcerRt = 6;
    private final int onRectConcerLb = 7;
    private final int onRectConcerRb = 8;
    private final int outsideRect = -1;
    private int pointLoc = outsideRect;
    private boolean fixAspectRatio = false;
    private RectFloat imageRect;

    /**
     * 构造
     *
     * @param context Context对象
     */
    public CropImageView(Context context) {
        super(context);
        init(context);
    }

    /**
     * 构造
     *
     * @param context Context对象
     * @param attrSet AttrSet对象
     */
    public CropImageView(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init(context);
    }

    /**
     * 构造
     *
     * @param context   Context对象
     * @param attrSet   AttrSet对象
     * @param styleName 风格
     */
    public CropImageView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(context);
    }

    private void init(Context context) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(new Color(Color.getIntColor("#aaffffff")));
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
        int mPaintWidth = 10;
        mPaint.setStrokeWidth(mPaintWidth);

        mPaintLine = new Paint();
        mPaintLine.setAntiAlias(true);
        mPaintLine.setColor(new Color(Color.getIntColor("#aaffffff")));
        mPaintLine.setStyle(Paint.Style.STROKE_STYLE);
        mPaintLine.setStrokeWidth(5);

        mPaintBg = new Paint();
        mPaintBg.setAntiAlias(true);
        mPaintBg.setColor(new Color(Color.getIntColor("#77000000")));
        mPaintBg.setStyle(Paint.Style.FILLANDSTROKE_STYLE);

        addDrawTask(this);
        setTouchEventListener(this);
        setEstimateSizeListener(this);
    }

    /**
     * 设置是否适应比例
     *
     * @param fixAspectRatio 是否适应比例
     */
    public void setFixedAspectRatio(boolean fixAspectRatio) {
        this.fixAspectRatio = fixAspectRatio;
        initRect();
        invalidate();
    }

    /**
     * 设置宽高比
     *
     * @param aspectRatioX 宽
     * @param aspectRatioY 高
     */
    public void setAspectRatio(int aspectRatioX, int aspectRatioY) {
        initRect(((double) aspectRatioY / (double) aspectRatioX));
        fixAspectRatio = true;

        invalidate();
    }

    /**
     * 设置图像
     *
     * @param pixelMap 原图
     * @param srcRect  原图对应的Rect
     */
    public void setImageBitmap(PixelMap pixelMap, RectFloat srcRect) {
        PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
        options.pixelFormat = PixelFormat.ARGB_8888;
        mDrawBit = PixelMap.create(pixelMap, options);
        imageRect = srcRect;
    }

    /**
     * 获取裁剪后的图像
     *
     * @param matrix Matrix
     * @return 效果图
     */
    public PixelMap getCroppedImage(Matrix matrix) {
        PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
        options.pixelFormat = PixelFormat.ARGB_8888;
        options.size = new Size((int) drawRect.getWidth(), (int) drawRect.getHeight());

//        ImageInfo info = mDrawBit.getImageInfo();

        Matrix3 cal = new Matrix3(matrix.getData());
        Matrix3 inverseMatrix = cal.inverseMatrix();
        Matrix matrix1 = new Matrix(inverseMatrix.getValues());

        float[] f = matrix1.getData();//getValues(f);
        float dx = f[2];//Matrix.MTRANS_X
        float dy = f[5];//Matrix.MTRANS_Y

        float scale_x = f[0];//Matrix.MSCALE_X
//        float scale_y = f[4];//Matrix.MSCALE_Y

        drawRect.scale(scale_x);
        drawRect.translate(dx, dy);

        ohos.media.image.common.Rect rect = new ohos.media.image.common.Rect((int) drawRect.left, (int) drawRect.top, (int) ((double) drawRect.right - (double) drawRect.left), (int) ((double) drawRect.bottom - (double) drawRect.top));
        return PixelMap.create(mDrawBit, rect, options);
    }

    private void initRect() {
        initRect(0D);
    }

    private double pro = 0.4D;


    private void initRect(double ratio) {
        if (getEstimatedWidth() == 0 || getEstimatedHeight() == 0) {
            return;
        }

        double centerX = ((double) imageRect.left + (double) imageRect.right) / 2;
        double centerY = ((double) imageRect.top + (double) imageRect.bottom) / 2;

        double height = ratio == 0 ? imageRect.getHeight() : (double) imageRect.getWidth() * ratio;

        double left = centerX - (double) imageRect.getWidth() * pro;
        double right = centerX + (double) imageRect.getWidth() * pro;
        double top = centerY - height * pro;
        double bottom = centerY + height * pro;
        RectFloat tmpRect = new RectFloat((float) left, (float) top, (float) right, (float) bottom);

        if (tmpRect.getHeight() > imageRect.getHeight()) {   //矩形框高超出
            double width = (double) imageRect.getHeight() * (1D / ratio);
            height = imageRect.getHeight();
            left = centerX - width * pro;
            right = centerX + width * pro;
            top = centerY - height * pro;
            bottom = centerY + height * pro;
            tmpRect = new RectFloat((float) left, (float) top, (float) right, (float) bottom);
        }

        drawRect = new RectFloat();
        drawRect.modify(tmpRect);
    }

    /**
     * 重置
     */
    public void reset() {
        if (mDrawBit != null && !mDrawBit.isReleased()) {
            mDrawBit.release();
        }

        this.invalidate();
    }

    @Override
    public void addDrawTask(DrawTask task) {
        super.addDrawTask(task);
        task.onDraw(this, mCanvasForTaskOverContent);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (mDrawBit == null || drawRect == null) return;
        // 距离原位置 矩形角 偏移像素
        double mMove = PxUtil.vp2px(5f);
        //四个角线条长度
        double mLineHeight = PxUtil.vp2px(14);

        double drawRectLeft = drawRect.left;
        double drawRectRight = drawRect.right;
        double drawRectTop = drawRect.top;
        double drawRectBottom = drawRect.bottom;

        //画横线
        canvas.drawLine((float) (drawRectLeft + mMove), (float) (drawRectTop + mMove), (float) (drawRectLeft + mMove + mLineHeight), (float) (drawRectTop + mMove), mPaintLine);
        canvas.drawLine((float) (drawRectRight - mLineHeight - mMove), (float) (drawRectTop + mMove), (float) (drawRectRight - mMove), (float) (drawRectTop + mMove), mPaintLine);
        canvas.drawLine((float) (drawRectLeft + mMove), (float) (drawRectBottom - mMove), (float) (drawRectLeft + mMove + mLineHeight), (float) (drawRectBottom - mMove), mPaintLine);
        canvas.drawLine((float) (drawRectRight - mLineHeight - mMove), (float) (drawRectBottom - mMove), (float) (drawRectRight - mMove), (float) (drawRectBottom - mMove), mPaintLine);

        //画竖线
        canvas.drawLine((float) (drawRect.left + mMove), (float) (drawRectTop + mMove), (float) (drawRect.left + mMove), (float) (drawRectTop + mMove + mLineHeight), mPaintLine);
        canvas.drawLine((float) (drawRect.left + mMove), (float) (drawRectBottom - mMove), (float) (drawRect.left + mMove), (float) (drawRectBottom - mMove - mLineHeight), mPaintLine);
        canvas.drawLine((float) (drawRectRight - mMove), (float) (drawRectTop + mMove), (float) (drawRectRight - mMove), (float) (drawRectTop + mMove + mLineHeight), mPaintLine);
        canvas.drawLine((float) (drawRectRight - mMove), (float) (drawRectBottom - mMove), (float) (drawRectRight - mMove), (float) (drawRectBottom - mMove - mLineHeight), mPaintLine);

        canvas.drawRect(0, 0, getWidth(), drawRect.top, mPaintBg);
        canvas.drawRect(0, drawRect.top, drawRect.left, getHeight(), mPaintBg);
        canvas.drawRect(drawRect.left, drawRect.bottom, getWidth(), getHeight(), mPaintBg);
        canvas.drawRect(drawRect.right, drawRect.top, getWidth(), drawRect.bottom, mPaintBg);

        canvas.drawRect(drawRect, mPaint);
    }

    @Override
    //onMeasure
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int width = EstimateSpec.getSize(widthMeasureSpec);
        int height = EstimateSpec.getSize(heightMeasureSpec);

        setEstimatedSize(
                EstimateSpec.getChildSizeWithMode(width, width, EstimateSpec.NOT_EXCEED),
                EstimateSpec.getChildSizeWithMode(height, height, EstimateSpec.NOT_EXCEED)
        );

        if (drawRect == null) {
            initRect();
        }

        return true;
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        boolean ret = false;//super.onTouchEvent(event);

        int[] location = getLocationOnScreen();     //获取组件在屏幕的绝对位置
        float positionX = (float) ((double) event.getPointerScreenPosition(event.getIndex()).getX() - location[0]);
        float positionY = (float) ((double) event.getPointerScreenPosition(event.getIndex()).getY() - location[1]);
        if (isOutsideView((int) positionX, (int) positionY)) {
            return ret;
        }

        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                last_x = positionX;
                last_y = positionY;
                pointLoc = getPointReactLoc(positionX, positionY);
                ret = true;
                break;
            case TouchEvent.POINT_MOVE:
                transformRect(positionX, positionY);
                last_x = positionX;
                last_y = positionY;
                getContext().getUITaskDispatcher().asyncDispatch(this::invalidate);//this.postInvalidate();
                ret = true;
                break;
            case TouchEvent.CANCEL:
            case TouchEvent.PRIMARY_POINT_UP:
                ret = false;
                break;
        }
        return ret;
    }

    private int getPointReactLoc(double x, double y) {
        int touchOffset = 50;   //触摸误差
        if (x > (double) drawRect.left + touchOffset && x < (double) drawRect.right - touchOffset &&
                y > (double) drawRect.top + touchOffset && y < (double) drawRect.bottom - touchOffset) {//触摸框内
            return insideRect;
        } else if (Math.abs(x - (double) drawRect.left) <= touchOffset && Math.abs(y - (double) drawRect.top) <= touchOffset) {
            return onRectConcerLt;
        } else if (Math.abs(x - (double) drawRect.left) <= touchOffset && Math.abs(y - (double) drawRect.bottom) <= touchOffset) {
            return onRectConcerLb;
        } else if (Math.abs(x - (double) drawRect.right) <= touchOffset && Math.abs(y - (double) drawRect.top) <= touchOffset) {
            return onRectConcerRt;
        } else if (Math.abs(x - (double) drawRect.right) <= touchOffset && Math.abs(y - (double) drawRect.bottom) <= touchOffset) {
            return onRectConcerRb;
        } else if (x >= (double) drawRect.left - touchOffset && x <= (double) drawRect.left + touchOffset) {
            return onRectXL;
        } else if (x >= (double) drawRect.right - touchOffset && x <= (double) drawRect.right + touchOffset) {
            return onRectXR;
        } else if (y > (double) drawRect.top - touchOffset && y < (double) drawRect.top + touchOffset) {
            return onRectYT;
        } else if (y > (double) drawRect.bottom - touchOffset && y < (double) drawRect.bottom + touchOffset) {
            return onRectYB;
        } else {
            return outsideRect;
        }
    }

    private void transformRect(double x, double y) {
        if (pointLoc == outsideRect) return;

        double offsetX = x - (double) last_x;
        double offsetY = y - (double) last_y;

        RectFloat tempRect = new RectFloat(drawRect);
        if (pointLoc == insideRect) {
            translateRect(tempRect, offsetX, offsetY);

        } else if (pointLoc == onRectXL) {

            if (fixAspectRatio) {
                offsetX = -offsetX;
                double scalX = offsetX / (double) drawRect.getWidth() + 1;
                double scalY = scalX;

                if (scalX > -1 && scalX < 3) {
                    RectUtil.scaleRect(tempRect, scalX, scalY);
                }
                tempRect.translate((float) ((double) drawRect.right - (double) tempRect.right), 0);
            } else {
                tempRect.left = (float) x;
            }

        } else if (pointLoc == onRectXR) {
            if (fixAspectRatio) {
                double scalX = offsetX / (double) drawRect.getWidth() + 1;
                double scalY = scalX;

                if (scalX > -1 && scalX < 3) {
                    RectUtil.scaleRect(tempRect, scalX, scalY);
                }
                tempRect.translate((float) ((double) drawRect.left - (double) tempRect.left), 0);
            } else {
                tempRect.right = (float) x;
            }
        } else if (pointLoc == onRectYT) {
            if (fixAspectRatio) {
                offsetY = -offsetY;
                double scalY = offsetY / (double) drawRect.getHeight() + 1;
                double scalX = scalY;

                if (scalY > -1 && scalY < 3) {
                    RectUtil.scaleRect(tempRect, scalX, scalY);
                }
                tempRect.translate(0, (float) ((double) drawRect.bottom - (double) tempRect.bottom));
            } else {
                tempRect.top = (float) y;
            }
        } else if (pointLoc == onRectYB) {

            if (fixAspectRatio) {
                double scalY = offsetY / (double) drawRect.getHeight() + 1;
                double scalX = scalY;

                if (scalY > -1 && scalY < 3) {
                    RectUtil.scaleRect(tempRect, scalX, scalY);
                }
                tempRect.translate(0, (float) ((double) drawRect.top - (double) tempRect.top));
            } else {
                tempRect.bottom = (float) y;
            }
        } else if (pointLoc == onRectConcerRb) {
            if (fixAspectRatio) {
                double scalX = offsetX / (double) drawRect.getWidth() + 1;
                double scalY = scalX;

                if (scalX > -1 && scalX < 3) {
                    RectUtil.scaleRect(tempRect, scalX, scalY);
                }
                tempRect.translate((float) ((double) drawRect.left - (double) tempRect.left), (float) ((double) drawRect.top - (double) tempRect.top));
            } else {
                tempRect.right = (float) x;
                tempRect.bottom = (float) y;
            }
        } else if (pointLoc == onRectConcerRt) {
            if (fixAspectRatio) {
                double scalX = offsetX / (double) drawRect.getWidth() + 1;
                double scalY = scalX;

                if (scalX > -1 && scalX < 3) {
                    RectUtil.scaleRect(tempRect, scalX, scalY);
                }
                tempRect.translate((float) ((double) drawRect.left - (double) tempRect.left), (float) ((double) drawRect.bottom - (double) tempRect.bottom));
            } else {
                tempRect.right = (float) x;
                tempRect.top = (float) y;
            }
        } else if (pointLoc == onRectConcerLb) {
            if (fixAspectRatio) {
                offsetX = -offsetX;
                double scalX = offsetX / (double) drawRect.getWidth() + 1;
                double scalY = scalX;

                if (scalX > -1 && scalX < 3) {
                    RectUtil.scaleRect(tempRect, scalX, scalY);
                }
                tempRect.translate((float) ((double) drawRect.right - (double) tempRect.right), (float) ((double) drawRect.top - (double) tempRect.top));
            } else {
                tempRect.left = (float) x;
                tempRect.bottom = (float) y;
            }
        } else if (pointLoc == onRectConcerLt) {
            if (fixAspectRatio) {
                offsetX = -offsetX;
                double scalX = offsetX / (double) drawRect.getWidth() + 1;
                double scalY = scalX;

                if (scalX > -1 && scalX < 3) {
                    RectUtil.scaleRect(tempRect, scalX, scalY);
                }
                tempRect.translate((float) ((double) drawRect.right - (double) tempRect.right), (float) ((double) drawRect.bottom - (double) tempRect.bottom));
            } else {
                tempRect.left = (float) x;
                tempRect.top = (float) y;
            }
        }

        if (tempRect.left < imageRect.left || tempRect.right > imageRect.right ||
                tempRect.top < imageRect.top || tempRect.bottom > imageRect.bottom) {
            return;
        }
        if (tempRect.getWidth() < 100 || tempRect.getHeight() < 100) {
            return;
        }

        drawRect.modify(tempRect);
    }

    private void translateRect(RectFloat tempRect, double offsetX, double offsetY) {
        if ((double) drawRect.right + offsetX < 0 || (double) drawRect.right + offsetX > getWidth() ||
                (double) drawRect.top + offsetY < 0 || (double) drawRect.bottom + offsetY > getHeight())
            return;
        RectUtil.translateRect(tempRect, offsetX, offsetY);
    }

    /**
     * touch是否移出view之外
     *
     * @param x x坐标
     * @param y y坐标
     * @return 是否移出view之外
     */
    private boolean isOutsideView(int x, int y) {
//        int[] location = getLocationOnScreen();
//        int pointx = x - location[0];
//        int pointy = y - location[1];
        boolean flag = false;
        Rect rect = new Rect();
        getSelfVisibleRect(rect);
        if (!rect.isInclude(x, y)) {
            flag = true;
        }
        return flag;
    }

}
